var crypto = require("crypto");
var conf = {
    iv: '2134215421321211',
    padding: 'PKCS7Padding'
}

module.exports = function(connector, options, server) {

    const scheduler = connector.ws_scheduler;
    const providers = connector.ws_providers;
    const consumers = connector.ws_consumers;

    const encrypt = function(data) {
        var chunks = [];
        var cipher = crypto.createCipheriv('aes-128-cbc', options.key, conf.iv);
        cipher.setAutoPadding(true);
        chunks.push(cipher.update(data));
        chunks.push(cipher.final());
        return Buffer.concat(chunks);
    }

    const decrypt = function(data) {
        var chunks = [];
        const decipher = crypto.createDecipheriv('aes-128-cbc', options.key, conf.iv);
        decipher.setAutoPadding(true);
        chunks.push(decipher.update(data));
        chunks.push(decipher.final());
        return Buffer.concat(chunks);
    }

    const respond = function(socket, id, error, result) {

        let message = {
            "id": id
        };

        if (error) {
            if (error.message) {
                error = error.message;
            }
            message.error = {
                "message": error
            };
        } else {
            message.result = result;
        }

        let payload = @.serialize(message);

        let serialized = @.hash.sha1(payload) + payload;
        let plain = Buffer.from(serialized, "utf8");

        if (options.key) {
            let encrypted = encrypt(plain);
            socket.send(encrypted.toString("base64"));
        } else {
            socket.send(plain.toString("base64"));
        }

    };

    return function(socket) {

        socket.rpcs = Object.create(null);

        socket.rpc = function(method, params, timeout) {

            return @.async(function() {

                let rpcID = @.uuid();

                socket.rpcs[rpcID] = (error, result) => {
                    delete socket.rpcs[rpcID];
                    if (error) {
                        if (error.message) {
                            error = error.message;
                        }
                        this.reject(new Error(error));
                    } else {
                        this.next(result);
                    }
                };

                let message = {
                    "id": rpcID,
                    "method": method,
                    "params": params
                };

                let payload = @.serialize(message);

                let serialized = @.hash.sha1(payload) + payload;
                let plain = Buffer.from(serialized, "utf8");

                setTimeout(() => {
                    if (!socket.rpcs[rpcID]) { return; }
                    delete socket.rpcs[rpcID];
                    this.reject(new Error("Request TIMEOUT : " + method));
                }, timeout ? timeout : 5000);


                if (options.key) {
                    socket.send(encrypt(plain).toString("base64"));
                } else {
                    socket.send(plain.toString("base64"));
                }

            });

        };

        socket.on("message", function(data) {

            if (typeof data !== "string") {
                // Ignore non-string message
                @warn("Non-string message recevied: " + @.jsonize(data));
                return;
            }

            try {
                
                let serialized = Buffer.from(data, "base64");

                if (options.key) {
                    serialized = decrypt(serialized);
                }
                serialized = serialized.toString("utf-8");

                let sign = serialized.slice(0, 40);
                let payload = serialized.slice(40);
                let message = @.deserialize(payload);

                @info("New Message recieved : ");
                @info(message);

                if (message.method) {
                    
                    switch (message.method) {
                        case "@broadcast" : {
                            if (socket.session.providerToken || !options.token) {

                                @.async(function(){
                                    if (consumers[socket.session.providerToken]) {
                                        return @.async.all(consumers[socket.session.providerToken],function(forward){
                                            forward.rpc(message.params.method,message.params.params, options.timeout).finished(this.next);
                                        })
                                    } else {
                                        this.next();
                                    }

                                }).then(function(){

                                    if ((!options.token && !socket.session.providerToken) || socket.session.providerToken == options.token){

                                        var methods = message.params.method.split(".");
                                        var bindingID = methods.reduce(function(root, name) {
                                            if (root[name]) {
                                                return root[name];
                                            } else {
                                                return {};
                                            }
                                        }, connector.services);

                                        if (bindingID && connector.bindings[bindingID]) {

                                            var binding = connector.bindings[bindingID](connector);
                                            @.async.ensure(binding[methods[methods.length - 1]].apply(binding, message.params.params)).finished(this.next);
                                        
                                        } else {
                                            this.next();
                                        }

                                    } else {
                                        this.next();
                                    }

                                }).finished(function(error){
                                    if (error) {
                                        @error("WSRPC Broadcast forward error");
                                        @error(error);
                                    } else {
                                        @debug("WSRPC Broadcast forward finished :" + message.params.method);
                                    }
                                    
                                })
                                
                                
                                return respond(socket, message.id);
                            } else {
                                return respond(socket, message.id, "invalid connection");
                            }
                        }
                        case "@consumer": {

                            socket.session.consumerToken = message.params[0] || "";
                            @info("New wsrpc consumer connected with token : " + socket.session.consumerToken);

                            if (providers[socket.session.consumerToken]) {

                                if (scheduler[socket.session.consumerToken] == undefined) {
                                    scheduler[socket.session.consumerToken] = 0;
                                }

                                if (scheduler[socket.session.consumerToken] >= providers[socket.session.consumerToken].length) {
                                    scheduler[socket.session.consumerToken] = 0;
                                }

                            } else if ((!options.token && !socket.session.consumerToken) || socket.session.consumerToken == options.token) {

                                if (scheduler[socket.session.consumerToken] == undefined) {
                                    scheduler[socket.session.consumerToken] = 0;
                                }
                            }

                            if (!consumers[socket.session.consumerToken]) {
                                consumers[socket.session.consumerToken] = [];
                            }

                            consumers[socket.session.consumerToken].push(socket);

                            return respond(socket, message.id);
                        }
                        case "@provider": {
                            
                            socket.session.providerToken = message.params[0] || "";

                            @info("New wsrpc provider connected with token : " + socket.session.providerToken);

                            if (socket.session.providerToken || !options.token) {

                                if (!providers[socket.session.providerToken]) {
                                    providers[socket.session.providerToken] = [];
                                }

                                providers[socket.session.providerToken].push(socket);

                                return respond(socket, message.id);

                            } else {
                                return respond(socket, message.id, "invalid token");
                            }
                        }
                        default: {

                            if ((providers[socket.session.consumerToken] && providers[socket.session.consumerToken].length) || scheduler[socket.session.consumerToken] != undefined) {

                                if (scheduler[socket.session.consumerToken] == undefined){
                                    scheduler[socket.session.consumerToken] = 0;
                                }

                                var total = providers[socket.session.consumerToken].length;

                                if ((!options.token && !socket.session.consumerToken) || (socket.session.consumerToken == options.token)) {
                                    total = total + 1;
                                }

                                scheduler[socket.session.consumerToken] = (++scheduler[socket.session.consumerToken]) % total;

                                if ((!options.token && !socket.session.consumerToken) || (socket.session.consumerToken == options.token)) {

                                    if (providers[socket.session.consumerToken][scheduler[socket.session.consumerToken]]) {

                                        providers[socket.session.consumerToken][scheduler[socket.session.consumerToken]].rpc(message.method, message.params, options.timeout).finished(function(error, result) {
                                            if (error) {
                                                respond(socket, message.id, error);
                                            } else {
                                                respond(socket, message.id, null, result);
                                            }
                                        })

                                    } else {
                                        // local call
                                        var methods = message.method.split(".");
                                        var bindingID = methods.reduce(function(root, name) {
                                            if (root[name]) {
                                                return root[name];
                                            } else {
                                                return {};
                                            }
                                        }, connector.services);

                                        if (bindingID && connector.bindings[bindingID]) {

                                            var binding = connector.bindings[bindingID](connector);

                                            @.async.ensure(binding[methods[methods.length - 1]].apply(binding, message.params)).finished(function(error, result) {
                                                if (error) {
                                                    respond(socket, message.id, error);
                                                } else {
                                                    respond(socket, message.id, null, result);
                                                }
                                            });

                                        } else {
                                            respond(socket, message.id, "Method not found");
                                        }
                                    }
                                } else {
                                    if (providers[socket.session.consumerToken][scheduler[socket.session.consumerToken]]) {
                                        providers[socket.session.consumerToken][scheduler[socket.session.consumerToken]].rpc(message.method, message.params, options.timeout).finished(function(error, result) {
                                            if (error) {
                                                respond(socket, message.id, error);
                                            } else {
                                                respond(socket, message.id, null, result);
                                            }
                                        })
                                    } else {
                                        respond(socket, message.id, "Provider not found");
                                    }
                                    
                                }

                            } else {
                                return respond(socket, message.id, "Method not found");
                            }
                        }
                    }

                } else if (socket.rpcs[message.id]) {

                    if (message.error) {
                        socket.rpcs[message.id](message.error);
                    } else {
                        socket.rpcs[message.id](undefined, message.result);
                    }
                }

            } catch (error) {
                @error(error);
            }

        }).on("close", function(code, message) {

            if (providers[socket.session.providerToken]) {

                let indexOfSocket = providers[socket.session.providerToken].indexOf(socket);

                if (indexOfSocket >= 0) {
                    providers[socket.session.providerToken].splice(indexOfSocket,1);
                }

                if (scheduler[socket.session.providerToken] && scheduler[socket.session.providerToken] >= providers[socket.session.providerToken].length){
                    scheduler[socket.session.providerToken] = 0;
                }

            }

            if (consumers[socket.session.consumerToken]) {
                let indexOfSocket = consumers[socket.session.consumerToken].indexOf(socket);

                if (indexOfSocket >= 0) {
                    consumers[socket.session.consumerToken].splice(indexOfSocket,1);
                }
            }

        }).on("error", function(error) {
            @error(error);
        });

    }
}